/**
 * @file Value.cc
 * @author Xiaoze Lin (linxiaoze96@gmail.com)
 * @brief The implementation of value class.
 * @version 0.1
 *
 * @copyright Copyright (c) 2024
 *
 */
#include "Value.hh"

namespace ifsim {

Value::Value() : _bit0(ALL_ONES_64), _bit1(0x0)  // default val 'X'
{
  ;
}

Value::Value(uint64_t bit0, uint64_t bit1) : _bit0(bit0), _bit1(bit1)
{
  ;
}

bool Value::operator==(const Value& other_value)
{
  return (_bit0 == other_value._bit0) && (_bit1 == other_value._bit1);
}

bool Value::operator!=(const Value& other_value)
{
  return (_bit0 != other_value._bit0) || (_bit1 != other_value._bit1);
}

//  single val is indicated by the 0th bit, other bits are set as X->(1,0)
//  so that value comparing can be done directly, rather than using mask
void Value::setSingle0()
{
  _bit0 = 0xfffffffffffffffe;
  _bit1 = 0x0;
}

void Value::setSingle1()
{
  _bit0 = 0xffffffffffffffff;
  _bit1 = 0x1;
}

void Value::setSingleX()
{
  _bit0 = 0xffffffffffffffff;
  _bit1 = 0x0;
}

void Value::setSingleZ()
{
  _bit0 = 0xfffffffffffffffe;
  _bit1 = 0x1;
}

void Value::setParallel0()
{
  _bit0 = ALL_ZEROS;
  _bit1 = ALL_ZEROS;
}

void Value::setParallel1()
{
  _bit0 = ALL_ONES_64;
  _bit1 = ALL_ONES_64;
}

void Value::setParallelX()
{
  _bit0 = ALL_ONES_64;
  _bit1 = ALL_ZEROS;
}

void Value::setParallelZ()
{
  _bit0 = ALL_ZEROS;
  _bit1 = ALL_ONES_64;
}

void Value::setVal1(int num_bits)
{
  _bit0 = ALL_ONES_64;
  _bit1 = ALL_ZEROS;

  if (num_bits == NUM_OF_BITS) {
    _bit1 = ALL_ONES_64;
  } else {
    uint64_t tmp_bit1 = 0x1;
    tmp_bit1 = (tmp_bit1 << num_bits) - 1;
    _bit1 |= tmp_bit1;
  }
}

void Value::setVal0(int num_bits)
{
  _bit0 = ALL_ONES_64;
  _bit1 = ALL_ZEROS;

  if (num_bits == NUM_OF_BITS) {
    _bit0 = ALL_ZEROS;
  } else {
    uint64_t tmp_bit0 = 0x1;
    tmp_bit0 = (tmp_bit0 << num_bits) - 1;
    _bit0 = _bit0 ^ tmp_bit0;
  }
}

void Value::setValue(uint64_t bit0, uint64_t bit1)
{
  _bit0 = bit0;
  _bit1 = bit1;
}

void Value::expandOneBitToValue(bool bit0, bool bit1)
{
  if (bit0 == 0 && bit1 == 0) {
    setParallel0();
  } else if (bit0 == 1 && bit1 == 1) {
    setParallel1();
  } else if (bit0 == 1 && bit1 == 0) {
    setParallelX();
  } else {
    setParallelZ();
  }
}

Value256::Value256()  // default val 'X'
{
  ;
}

Value256::Value256(__m256i_wrapper bit0, __m256i_wrapper bit1) : _bit0(bit0), _bit1(bit1)
{
  ;
}

bool Value256::operator==(const Value256& other_value)
{
  __m256i tmp, all_1;
  all_1 = _mm256_set1_epi64x(ALL_ONES_64);
  tmp = _mm256_cmpeq_epi64(_bit0.int256, other_value._bit0.int256);
  int comp = _mm256_testc_si256(tmp, all_1);
  tmp = _mm256_cmpeq_epi64(_bit1.int256, other_value._bit1.int256);
  comp = _mm256_testc_si256(tmp, all_1) && comp;
  return (comp == 1);
}

bool Value256::operator!=(const Value256& other_value)
{
  __m256i tmp, all_1;
  all_1 = _mm256_set1_epi64x(ALL_ONES_64);
  tmp = _mm256_cmpeq_epi64(_bit0.int256, other_value._bit0.int256);
  int comp = _mm256_testc_si256(tmp, all_1);
  tmp = _mm256_cmpeq_epi64(_bit1.int256, other_value._bit1.int256);
  comp = _mm256_testc_si256(tmp, all_1) && comp;
  return (comp == 0);
}

//  single val is indicated by the 0th bit, other bits are set as X->(1,0)
//  so that value comparing can be done directly, rather than using mask
void Value256::setSingle0()
{
  uint64_t single_0_bit0 = 0xfffffffffffffffe;
  _bit0.int256 = _mm256_set_epi64x(ALL_ONES_64, ALL_ONES_64, ALL_ONES_64, single_0_bit0);
  _bit1.int256 = _mm256_set1_epi64x(ALL_ZEROS);
}

void Value256::setSingle1()
{
  uint64_t single_1_bit1 = 0x1;
  _bit0.int256 = _mm256_set1_epi64x(ALL_ONES_64);
  _bit1.int256 = _mm256_set_epi64x(ALL_ZEROS, ALL_ZEROS, ALL_ZEROS, single_1_bit1);
}

void Value256::setSingleX()
{
  _bit0.int256 = _mm256_set1_epi64x(ALL_ONES_64);
  _bit1.int256 = _mm256_set1_epi64x(ALL_ZEROS);
}

void Value256::setSingleZ()
{
  uint64_t single_z_bit0 = 0xfffffffffffffffe;
  uint64_t single_z_bit1 = 0x1;
  _bit0.int256 = _mm256_set_epi64x(ALL_ONES_64, ALL_ONES_64, ALL_ONES_64, single_z_bit0);
  _bit1.int256 = _mm256_set_epi64x(ALL_ZEROS, ALL_ZEROS, ALL_ZEROS, single_z_bit1);
}

void Value256::set256Parallel0()
{
  _bit0.int256 = _mm256_setzero_si256();
  _bit1.int256 = _mm256_setzero_si256();
}

void Value256::set256Parallel1()
{
  _bit0.int256 = _mm256_set1_epi64x(ALL_ONES_64);
  _bit1.int256 = _mm256_set1_epi64x(ALL_ONES_64);
}

void Value256::set256ParallelX()
{
  _bit0.int256 = _mm256_set1_epi64x(ALL_ONES_64);
  _bit1.int256 = _mm256_setzero_si256();
}

void Value256::set256ParallelZ()
{
  _bit0.int256 = _mm256_setzero_si256();
  _bit1.int256 = _mm256_set1_epi64x(ALL_ONES_64);
}

void Value256::setVal1(int num_bits)
{
  _bit0.int256 = _mm256_set1_epi64x(ALL_ONES_64);

  if (num_bits == NUM_OF_BITS_256) {
    _bit1.int256 = _mm256_set1_epi64x(ALL_ONES_64);
  } else {
    uint64_t tmp_bit1 = 0x1;
    int num_shift_bits = num_bits % 64;
    tmp_bit1 = (tmp_bit1 << num_shift_bits) - 1;
    if (num_bits < 64) {
      _bit1.int256 = _mm256_set_epi64x(ALL_ZEROS, ALL_ZEROS, ALL_ZEROS, tmp_bit1);
    } else if (num_bits < 128) {
      _bit1.int256 = _mm256_set_epi64x(ALL_ZEROS, ALL_ZEROS, tmp_bit1, ALL_ONES_64);
    } else if (num_bits < 192) {
      _bit1.int256 = _mm256_set_epi64x(ALL_ZEROS, tmp_bit1, ALL_ONES_64, ALL_ONES_64);
    } else {
      _bit1.int256 = _mm256_set_epi64x(tmp_bit1, ALL_ONES_64, ALL_ONES_64, ALL_ONES_64);
    }
  }
}

void Value256::setVal0(int num_bits)
{
  _bit1.int256 = _mm256_setzero_si256();

  if (num_bits == NUM_OF_BITS_256) {
    _bit0.int256 = _mm256_setzero_si256();
  } else {
    uint64_t tmp_bit0 = 0x1;
    int num_shift_bits = num_bits % 64;
    tmp_bit0 = (tmp_bit0 << num_shift_bits) - 1;
    tmp_bit0 ^= ALL_ONES_64;
    if (num_bits < 64) {
      _bit0.int256 = _mm256_set_epi64x(ALL_ONES_64, ALL_ONES_64, ALL_ONES_64, tmp_bit0);
    } else if (num_bits < 128) {
      _bit0.int256 = _mm256_set_epi64x(ALL_ONES_64, ALL_ONES_64, tmp_bit0, ALL_ZEROS);
    } else if (num_bits < 192) {
      _bit0.int256 = _mm256_set_epi64x(ALL_ONES_64, tmp_bit0, ALL_ZEROS, ALL_ZEROS);
    } else {
      _bit0.int256 = _mm256_set_epi64x(tmp_bit0, ALL_ZEROS, ALL_ZEROS, ALL_ZEROS);
    }
  }
}

void Value256::setValue(__m256i_wrapper bit0, __m256i_wrapper bit1)
{
  _bit0 = bit0;
  _bit1 = bit1;
}

void Value256::setOneBitVal(uint64_t bit_val, uint64_t other_bit_val, int bit_id)
{
  if (bit_id < 64) {
    _bit0.int256 = _mm256_set_epi64x(other_bit_val, other_bit_val, other_bit_val, bit_val);
    _bit1.int256 = _mm256_set_epi64x(other_bit_val, other_bit_val, other_bit_val, bit_val);
  } else if (bit_id < 128) {
    _bit0.int256 = _mm256_set_epi64x(other_bit_val, other_bit_val, bit_val, other_bit_val);
    _bit1.int256 = _mm256_set_epi64x(other_bit_val, other_bit_val, bit_val, other_bit_val);
  } else if (bit_id < 192) {
    _bit0.int256 = _mm256_set_epi64x(other_bit_val, bit_val, other_bit_val, other_bit_val);
    _bit1.int256 = _mm256_set_epi64x(other_bit_val, bit_val, other_bit_val, other_bit_val);
  } else {  // < 255
    _bit0.int256 = _mm256_set_epi64x(bit_val, other_bit_val, other_bit_val, other_bit_val);
    _bit1.int256 = _mm256_set_epi64x(bit_val, other_bit_val, other_bit_val, other_bit_val);
  }
}

void Value256::expandValueToValue256(Value val)
{
  _bit0.int256 = _mm256_set1_epi64x(val.bit0());
  _bit1.int256 = _mm256_set1_epi64x(val.bit1());
}

void Value256::expandOneBitToValue256(bool bit0, bool bit1)
{
  if (bit0 == 0 && bit1 == 0) {
    set256Parallel0();
  } else if (bit0 == 1 && bit1 == 1) {
    set256Parallel1();
  } else if (bit0 == 1 && bit1 == 0) {
    set256ParallelX();
  } else {
    set256ParallelZ();
  }
}
}  // namespace ifsim